/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.throwableset.tags;

import java.util.*;

import br.ufpe.cin.imlm2.jbeat.soot.path.ExceptionPath;
import br.ufpe.cin.imlm2.jbeat.soot.path.LinkedExceptionPath;

import soot.SootClass;
import soot.SootMethod;
import soot.tagkit.AttributeValueException;
import soot.tagkit.Tag;

/**
 * A tag that contains all the possible exceptions that a given method may
 * throw.
 * 
 * @author Irineu
 * 
 */
public class ThrowableSetTag implements Tag {

	public static final String TAG_NAME = "ThrowableSetTag";

	private Set<ExceptionPath> generated;
	private Set<ExceptionPath> thrown;
	private Set<ExceptionPath> propagated;
	private SootMethod source;

	public ThrowableSetTag(SootMethod src) {
		this.generated = new HashSet<ExceptionPath>();
		this.thrown = new HashSet<ExceptionPath>();
		this.propagated = new HashSet<ExceptionPath>();
		this.source = src;
	}

	public void addAllGeneratedExceptions(Set<SootClass> exceptions) {
		for (SootClass sootClass : exceptions) {
			this.addGeneratedException(sootClass);
		}
	}
	
	public void addGeneratedException(SootClass e) {
		if(!this.exceptionPathSetContains(generated, e))
			this.generated.add(new LinkedExceptionPath(null, e, this.source));
	}

	public void removeGeneratedException(SootClass e) {
		this.removePathsFor(this.generated, e);
	}


	public void addAllThrownExceptions(Set<SootClass> exceptions) {
		for (SootClass sootClass : exceptions) {
			this.addThrownException(sootClass);
		}
	}

	public void addThrownException(SootClass thrown) {
		if(!this.exceptionPathSetContains(this.thrown, thrown))
			this.thrown.add(new LinkedExceptionPath(null, thrown, this.source));
	}

	public void removeThrownException(SootClass exception) {
		this.removePathsFor(this.thrown, exception);
	}

	public void addPropagatedException(SootClass exception, SootMethod source) {
		Set<ExceptionPath> expPaths = ((ThrowableSetTag)source.getTag(TAG_NAME)).getPaths(exception);
		for (ExceptionPath expPath : expPaths) {
			this.propagated.add(new LinkedExceptionPath((LinkedExceptionPath) expPath, 
				exception, this.source));
		}
	}
	
	public void addAllPropagatedExceptions(Set<SootClass> exceptions,
			SootMethod source) {
		for (SootClass exception : exceptions) {
			this.addPropagatedException(exception, source);
		}
	}

	public void removePropagatedException(SootClass exception) {
		this.removePathsFor(this.propagated, exception);
	}

	public Set<SootClass> getThrowableSet() {
		Set<SootClass> throwableSet = new HashSet<SootClass>();
		throwableSet.addAll(this.getAllGeneratedExceptions());
		throwableSet.addAll(this.getAllThrownExceptions());
		throwableSet.addAll(this.getAllPropagatedExceptions());
		return throwableSet;
	}
	
	public Set<SootClass> getAllGeneratedExceptions() {
		return this.extractExceptionsFromPaths(this.generated);
	}

	public Set<SootClass> getAllThrownExceptions() {
		return this.extractExceptionsFromPaths(this.thrown);
	}

	public Set<SootClass> getAllPropagatedExceptions() {
		return this.extractExceptionsFromPaths(this.propagated);
	}
	
	public Map<SootClass, Set<SootMethod>> getPropagatedMap() {
		Map<SootClass, Set<SootMethod>> propagatedMap = new HashMap<SootClass,Set<SootMethod>>();
		return propagatedMap;
	}
	
	public Set<ExceptionPath> getPaths(SootClass exception){
		Set<ExceptionPath> paths = new HashSet<ExceptionPath>();
		for (ExceptionPath exceptionPath : this.generated) {
			if(exceptionPath.getException().equals(exception))
				paths.add(exceptionPath);
		}
		for (ExceptionPath exceptionPath : this.thrown) {
			if(exceptionPath.getException().equals(exception))
				paths.add(exceptionPath);
		}
		for (ExceptionPath exceptionPath : this.propagated) {
			if(exceptionPath.getException().equals(exception))
				paths.add(exceptionPath);
		}
		return paths;
	}
	
	public void removePathsFor(Set<ExceptionPath> expPaths , SootClass exception){
		Iterator<ExceptionPath> it = expPaths.iterator();
		while(it.hasNext()){
			if(it.next().getException().equals(exception)){
				it.remove();
			}
		}
	}
	
	private Set<SootClass> extractExceptionsFromPaths(Set<ExceptionPath> expPaths){
		Set<SootClass> generatedSet = new HashSet<SootClass>();
		for (ExceptionPath expPath : expPaths) {
			generatedSet.add(expPath.getException());
		}
		return generatedSet;
	}
	
	private boolean exceptionPathSetContains(Set<ExceptionPath> set,SootClass exception){
		for (ExceptionPath exceptionPath : set) {
			if(exceptionPath.getException().equals(exception))
				return true;
		}
		return false;
	}

	@Override
	public String getName() {
		return TAG_NAME;
	}

	@Override
	public byte[] getValue() throws AttributeValueException {
		return this.toString().getBytes();
	}

	@Override
	public String toString() {
		Iterator<SootClass> excepts = this.getThrowableSet().iterator();
		StringBuffer sb = new StringBuffer("{");
		while (excepts.hasNext()) {
			SootClass next = excepts.next();
			sb.append(next.getName() + ",");
		}
		if (sb.length() > 1)
			sb.deleteCharAt(sb.length() - 1);
		sb.append("}");
		return sb.toString();
	}

}
